Skip to content

feat(runner): isolate runtime deps to fix workspace monorepo failures#1381

Draft
Michael Price (michael-pr) wants to merge 14 commits into
mainfrom
wiz-10907-potential-incompatibility-with-other-monorepo-and-single
Draft

feat(runner): isolate runtime deps to fix workspace monorepo failures#1381
Michael Price (michael-pr) wants to merge 14 commits into
mainfrom
wiz-10907-potential-incompatibility-with-other-monorepo-and-single

Conversation

@michael-pr

@michael-pr Michael Price (michael-pr) commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Description drafted with AI, edited and reviewed by me.

Relates to WIZ-10907

Resolves WIZ-10849

Overview of Changes

Running qawolf flows run could fail with "Could not find Playwright", and more broadly, users don't want the CLI installing its dependencies into their own project. The cause: the CLI installed its runtime (Playwright, etc.) into the user's project, and in workspace monorepos (pnpm/yarn/npm) those installs get hoisted to the repo root — leaving the package's own node_modules empty, so resolution failed.

This PR moves the runtime into an isolated location the CLI owns (<data-dir>/runtime/<version-hash>), installed once on demand. The user's project is never written to. If a project already has the exact runtime installed it's reused as-is; otherwise the isolated dir is used. QAWOLF_RUNTIME_DIR relocates that dir (e.g. CI / read-only home), and --deps <dir> lets you supply your own.

Out of scope (follow-ups): resolving a bare import "playwright" inside a flow in the binary, and GC of stale runtime dirs after a version bump.

Testing

  1. In a pnpm/yarn workspace monorepo with a flow in a leaf package (no leaf node_modules), run qawolf flows run <pattern> — the flow passes, a note confirms the managed runtime is in use, and nothing is written into the repo.
  2. qawolf flows run --deps <prepared-dir> <pattern> — uses the given dir, installs nothing.
  3. QAWOLF_RUNTIME_DIR=<dir> qawolf flows run <pattern> — installs/uses the runtime under that dir.
  4. qawolf doctor — reports the managed runtime location without installing.

Checklist

  • Changes follow the code style of this project
  • Self-review completed
  • Tests added/updated (or not applicable)
  • No breaking changes (or described below)

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR introduces a managed runtime environment system that replaces the per-project ensureFlowDeps mechanism. A new runtimeEnv domain adds pinnedPackages (canonical versioned runtime dependencies), managedEnvHash() and managedEnvDir() for deterministic SHA-256-versioned placement under platform data path with QAWOLF_RUNTIME_DIR override, installPinned() (concurrency-safe npm installer with PID-scoped temp directories and atomic rename), ensureRuntimeEnv() (override → project → managed directory resolution with optional installation), and resolveDepsRootIfPresent() (read-only diagnostic probe). RunnerDeps gains depsRoot, threaded into initFlowRuntime() (accepts optional depsRoot override before upward walk) and loadFlowDefault() (switched to object-based API with optional bundleFlow callback). All commands (flows run, hybrid run, install all/android/browsers, doctor) migrate from ensureFlowDeps and resolveUniqueEnvDir to resolveDepsRoot() or resolveDepsRootIfPresent(), removing fail-fast errors on multi-package resolution. A new --deps <dir> CLI flag added to flows run. buildFlowsRunDeps() module assembles runner orchestration dependencies and routes reporter output through command UI. detectPackageManager and ensureFlowDeps removed from ensureDeps.ts, replaced by resolveProjectDirSafe(). New clearRuntimeEnv() function and install clear command remove managed runtime cache with destructive confirmation. Flow staging via stageFlows() prepares .qawolf/.local/<hash>-<pid> project copies with exclusions and path remapping. linkManagedDeps() creates idempotent node_modules symlinks from bundle roots to managed deps roots. copyDirExcluding() utility enables per-entry recursive copy with basename exclusion filters. Confirm UI removes selectKey in favor of arrow-key clack.confirm() with initialValue. shimDeps refactored with extracted resolveBunBuild() helper. Design document outlines layered node_modules strategy, binary executor resolution blockers with spike-gated branching, and phased implementation plan.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title is specific, uses imperative mood, describes the main change (isolate runtime deps to fix monorepo failures), and is under 72 characters.
Description check ✅ Passed Description includes overview of changes, concrete testing steps, all checklist items completed, and references related/resolved issues.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands.

@michael-pr

Copy link
Copy Markdown
Contributor Author

CodeRabbit (@coderabbitai) review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/domains/runner/initFlowRuntime.ts (1)

116-123: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Include depsRoot in the init cache key

Line 120 keys initCache only by startDir, but Line 122 passes options.depsRoot into doInit. Calls from the same directory with different depsRoot values can incorrectly reuse the first initialization and load the wrong runner.

Suggested fix
 export function initFlowRuntime(
   flowPath: string,
   options: InitFlowRuntimeOptions,
   fs: Fs = makeDefaultFs(),
 ): Promise<void> {
   const startDir = path.dirname(flowPath);
+  const depsKey =
+    options.depsRoot === undefined ? "" : path.resolve(options.depsRoot);
+  const cacheKey = `${startDir}::${depsKey}`;
   // Cache key is startDir, not fs — tests reusing the same startDir must call
   // _resetInitCache() between runs. Timeout is omitted deliberately: it is a
   // single run-global flag, so every flow in a process shares one value.
-  let p = initCache.get(startDir);
+  let p = initCache.get(cacheKey);
   if (!p) {
     p = doInit(flowPath, options.timeout, fs, options.depsRoot);
-    initCache.set(startDir, p);
+    initCache.set(cacheKey, p);
   }
   return p;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/domains/runner/initFlowRuntime.ts` around lines 116 - 123, The initCache
key in the initFlowRuntime function only includes startDir, but doInit receives
both startDir and options.depsRoot as parameters. When the same directory is
initialized with different depsRoot values, the cache incorrectly reuses the
first initialization. Create a composite cache key that combines both startDir
and options.depsRoot, then use this composite key in both the initCache.get()
call and the initCache.set() call to ensure different depsRoot values for the
same directory produce separate cache entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/install/all.ts`:
- Around line 57-59: Move the deps.ensureRuntimeEnv() call and depsRoot
assignment from its current position at line 57 to after the flow target
detection logic (after the hasWeb and hasAndroid checks around lines 73/82).
This ensures runtime environment resolution only occurs when there are actually
installable flows (web or android targets present), preventing unnecessary
managed installs or install failures when only iOS flows or no flows exist.
Update any references to depsRoot to use the value obtained from the deferred
ensureRuntimeEnv call.

In `@src/commands/install/browsers.ts`:
- Around line 21-41: The runtime environment resolution via ensureRuntimeEnv is
being called too early before confirming whether browser flows actually exist.
Move the ensureRuntimeEnv call and the subsequent resolvePlaywrightCli call to
execute lazily after browser targets have been confirmed to exist. This defers
unnecessary work when browser collection is a no-op and prevents offline
failures on otherwise skippable runs. Identify where browser targets are
actually validated in the flow and relocate the depsRoot resolution and runtime
environment setup to occur only after that point.

In `@src/domains/runtimeEnv/ensureRuntimeEnv.ts`:
- Around line 64-65: After the install(managed) call completes successfully, add
a validation check to ensure all pinned dependencies are actually resolved
before returning installed: true. Use the allPinnedResolved function to verify
that the install operation fully materialized the pinned deps for the managed
depsRoot. If the validation check fails, throw an error or fail fast to prevent
reporting success when dependencies were not fully installed.

In `@src/domains/runtimeEnv/installPinned.ts`:
- Around line 35-40: The readiness check in the `installPinned` function at line
35-40 uses `existsSync` to verify that `node_modules/.bin/playwright` exists,
but this is insufficient because the binary could exist while pinned versions
are missing or mismatched. Replace the `existsSync` check on the playwright
binary path with a call to `allPinnedResolved` to properly validate that all
pinned dependencies are correctly installed and resolved. Apply the same fix to
the duplicate check mentioned at lines 64-67.
- Around line 52-54: The error message in the Error constructor that handles
failed managed runtime installation is directly interpolating raw stderr output
from npm, which can expose sensitive information. Replace the raw
`result.stderr.trim()` in the thrown error message with a generic sanitized
error summary. If detailed stderr information is needed for debugging, move the
raw stderr to a separate debug logging call using controlled logging paths,
keeping the thrown error message safe and user-facing while preserving debugging
capabilities.

In `@src/domains/runtimeEnv/resolvePinned.ts`:
- Around line 38-40: In the resolvePinned.ts file, the fs.existsSync check that
verifies the playwright binary existence only looks for the extension-less
playwright script. Modify this condition to check for both the extension-less
variant and the .cmd variant (playwright.cmd) since Windows npm/bun
installations create the .cmd shim alongside or instead of the extension-less
version. Update the condition to return false only if neither variant exists,
allowing the check to properly detect Playwright installations on both Windows
and POSIX systems.

---

Outside diff comments:
In `@src/domains/runner/initFlowRuntime.ts`:
- Around line 116-123: The initCache key in the initFlowRuntime function only
includes startDir, but doInit receives both startDir and options.depsRoot as
parameters. When the same directory is initialized with different depsRoot
values, the cache incorrectly reuses the first initialization. Create a
composite cache key that combines both startDir and options.depsRoot, then use
this composite key in both the initCache.get() call and the initCache.set() call
to ensure different depsRoot values for the same directory produce separate
cache entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 63fe5f4a-7d7c-4936-a062-1d63212bee77

📥 Commits

Reviewing files that changed from the base of the PR and between 151f99d and 6e6b2c6.

⛔ Files ignored due to path filters (1)
  • src/commands/__snapshots__/help.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (47)
  • knip.config.ts
  • src/commands/doctor/handler.ts
  • src/commands/flows/hybridRun.test.ts
  • src/commands/flows/hybridRunDefaults.ts
  • src/commands/flows/run.register.ts
  • src/commands/flows/runDefaults.handle.test.ts
  • src/commands/flows/runDefaults.reporterWiring.test.ts
  • src/commands/flows/runDefaults.ts
  • src/commands/install/all.test.ts
  • src/commands/install/all.ts
  • src/commands/install/android.ts
  • src/commands/install/browsers.ts
  • src/core/messages/flows.ts
  • src/core/messages/runner.ts
  • src/core/paths.ts
  • src/domains/flows/ensureDeps.test.ts
  • src/domains/flows/ensureDeps.ts
  • src/domains/runner/createRunner.guards.test.ts
  • src/domains/runner/createRunner.test.ts
  • src/domains/runner/initFlowRuntime.test.ts
  • src/domains/runner/initFlowRuntime.ts
  • src/domains/runner/loadFlowDefault.test.ts
  • src/domains/runner/loadFlowDefault.ts
  • src/domains/runner/runAndroidFlow.test.ts
  • src/domains/runner/runAndroidFlow.ts
  • src/domains/runner/runAndroidFlowDeps.ts
  • src/domains/runner/runInternals.ts
  • src/domains/runner/runWebFlow.fixtures.ts
  • src/domains/runner/runWebFlow.ts
  • src/domains/runner/runWebFlowDeps.ts
  • src/domains/runner/runnerDeps.test.ts
  • src/domains/runner/runnerDeps.ts
  • src/domains/runner/types.ts
  • src/domains/runtimeEnv/ensureRuntimeEnv.test.ts
  • src/domains/runtimeEnv/ensureRuntimeEnv.ts
  • src/domains/runtimeEnv/index.ts
  • src/domains/runtimeEnv/installPinned.test.ts
  • src/domains/runtimeEnv/installPinned.ts
  • src/domains/runtimeEnv/managedEnvDir.test.ts
  • src/domains/runtimeEnv/managedEnvDir.ts
  • src/domains/runtimeEnv/pinnedPackages.ts
  • src/domains/runtimeEnv/resolveDepsRootIfPresent.test.ts
  • src/domains/runtimeEnv/resolveDepsRootIfPresent.ts
  • src/domains/runtimeEnv/resolvePinned.test.ts
  • src/domains/runtimeEnv/resolvePinned.ts
  • src/domains/runtimeEnv/shimDeps.test.ts
  • src/domains/runtimeEnv/shimDeps.ts
💤 Files with no reviewable changes (2)
  • src/core/messages/flows.ts
  • src/domains/flows/ensureDeps.ts

Comment thread src/commands/install/all.ts Outdated
Comment thread src/commands/install/browsers.ts Outdated
Comment thread src/domains/runtimeEnv/ensureRuntimeEnv.ts
Comment thread src/domains/runtimeEnv/installPinned.ts
Comment thread src/domains/runtimeEnv/installPinned.ts
Comment thread src/domains/runtimeEnv/resolvePinned.ts Outdated
@michael-pr

Copy link
Copy Markdown
Contributor Author

CodeRabbit (@coderabbitai) review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/doctor/handler.ts`:
- Around line 44-47: The issue is in the resolveDepsRootIfPresent call where
when projectDir is undefined, an empty object is passed as the second argument
instead of providing a fallback. When resolveProjectDirSafe returns undefined,
you should provide the current working directory (cwd) as a fallback value for
projectDir in the ternary operator passed to resolveDepsRootIfPresent, so that
it can still probe for dependencies in the current workspace instead of skipping
the project probe entirely and only checking managed runtime.

In `@src/domains/flows/ensureDeps.ts`:
- Around line 43-46: The catch block around the resolveUniqueEnvDir function
call is too broad and silently masks all errors, including unexpected filesystem
or logic errors. Narrow the catch block to only handle the expected
multi-package conflict error by checking the error message or type before
returning undefined. For unexpected errors, re-throw them so they surface as
actual failures rather than silently falling back to managed runtime. Ideally,
work with the resolveUniqueEnvDir function to use a dedicated error class or
code for the multi-package conflict scenario and match against that specific
error type instead of catching all exceptions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dd85e8fb-94b8-4eef-9d45-fa9b8af7fefa

📥 Commits

Reviewing files that changed from the base of the PR and between 6e6b2c6 and 445f49a.

📒 Files selected for processing (29)
  • src/commands/doctor/handler.ts
  • src/commands/flows/buildFlowsRunDeps.ts
  • src/commands/flows/hybridRun.test.ts
  • src/commands/flows/hybridRunDefaults.ts
  • src/commands/flows/runDefaults.handle.test.ts
  • src/commands/flows/runDefaults.reporterWiring.test.ts
  • src/commands/flows/runDefaults.ts
  • src/commands/install/all.test.ts
  • src/commands/install/all.ts
  • src/commands/install/android.ts
  • src/commands/install/browsers.fixtures.ts
  • src/commands/install/browsers.ts
  • src/commands/resolveDepsRoot.test.ts
  • src/commands/resolveDepsRoot.ts
  • src/domains/flows/ensureDeps.ts
  • src/domains/install/android/index.ts
  • src/domains/install/android/installAndroid.test.ts
  • src/domains/install/browsers.ts
  • src/domains/runner/loadFlowDefault.ts
  • src/domains/runner/runInternals.ts
  • src/domains/runtimeEnv/ensureRuntimeEnv.test.ts
  • src/domains/runtimeEnv/ensureRuntimeEnv.ts
  • src/domains/runtimeEnv/index.ts
  • src/domains/runtimeEnv/installPinned.test.ts
  • src/domains/runtimeEnv/installPinned.ts
  • src/domains/runtimeEnv/resolveDepsRootIfPresent.ts
  • src/domains/runtimeEnv/resolvePinned.test.ts
  • src/domains/runtimeEnv/resolvePinned.ts
  • src/domains/runtimeEnv/shimDeps.ts
💤 Files with no reviewable changes (1)
  • src/domains/runtimeEnv/index.ts

Comment thread src/commands/doctor/handler.ts
Comment thread src/domains/flows/ensureDeps.ts
@michael-pr

Michael Price (michael-pr) commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

✅ Manual verification — monorepo / multi-package compatibility

Reproduced the reported issue (flows run failing to find Playwright in a workspace monorepo, where installing deps at the repo root is not acceptable) and verified the fix end-to-end against a matrix of repo shapes. Each run uses a self-contained flow that navigates to example.com (no QA Wolf account needed — local flows run uses withContext, not withAuthContext), so the whole path is exercised: deps resolution → managed install → browser install → flow execution.

Headline before/after — workspace monorepo, flows at repo root, no playwright

BEFORE (main binary):

flows run
[1/1] Preparing environment
ERROR  Bundle failed        ← failed on first attempt

…and on retry it "worked" only by installing 587 MB of node_modules into the monorepo root — the behavior the reported issue called out:

OLD binary mutated the monorepo root:
587M    02-monorepo-toplevel/node_modules
@qawolf packages installed into the repo root:
  emails  flows  testkit  run-globals-android  …  (+ appium, playwright, …)

AFTER (this branch):

flows run
[1/1] Preparing environment
Environment ready
Runtime: QA Wolf installed its runtime in a managed location (your project is untouched):
  …/qawolf-nodejs/runtime/7c171b8defdd8ce5
[1/1] Install chromium
Installed 1 browser.
Running 1 flow
  ✓ Smoke passed (1.03s)
→ repo root after the run: CLEAN — no node_modules created in the project

Full scenario matrix (all on this branch's binary)

# Repo shape Result Project mutated?
1 Simple single-app, no playwright ✓ flow passed (1.01s); managed runtime installed at exact pinned versions No
2 Monorepo, flows at repo root, no playwright (the reported issue) ✓ flow passed (1.03s) — see before/after above No (was 587 MB on main)
3 Flows span multiple packages (multi-package pattern) ✓ 2/2 passed — graceful managed fallback instead of the old hard error No
4 Project already has playwright@1.40.0 (≠ pinned 1.58.2) ✓ flow passed (0.95s) via managed fallback; existing playwright@1.40.0 left untouched No
5a --deps <valid prepared dir> ✓ uses the override directly, no install, flow passed (0.71s) No
5b --deps <invalid dir> ✓ clear actionable error, not a crash: --deps directory … is missing required pinned dependencies. Run 'npm install' in that directory…
6 pnpm workspace monorepo (pnpm-lock.yaml) ✓ flow passed (1.00s); managed install uses npm internally regardless of project PM No
qawolf doctor (rerouted through resolveDepsRootIfPresent) PASS playwright 1.58.2 resolved from managed dir; all checks pass No

What proves the issue won't recur

  • Deps now resolve override → project → CLI-managed runtime dir (getDataDir()/runtime/<versionHash>). The managed dir is project-independent, so it works identically no matter where flows live or how the monorepo is laid out — the same …/runtime/7c171b8defdd8ce5 was reused across every scenario above.
  • The CLI never installs into the project unless that project already has all pinned packages at exact versions — so it can't pollute a monorepo root, and it can't fight a project's existing playwright.
  • Multi-package patterns that used to throw now fall back to the managed runtime gracefully.

Backwards-compatibility & tests

  • /compat review: ship-with-caveats. No external contract breaks — every changed signature (loadFlowDefault, createRunnerDeps, RunnerDeps.depsRoot, install/browsers/android deps) is internal and all call sites are updated in the same changeset. The new runtimeEnv layer is comprehensively unit-tested (override valid/invalid, project, managed install-on-first-use, install-incomplete→throw, hash determinism, shim detection, multi-package fallback).
  • Full suite: 1022 pass / 0 fail (139 files).

Verification repos and captured output retained locally under /tmp/qawolf-compat/evidence/. Comparison run against main @ 151f99d9.

@michael-pr

Copy link
Copy Markdown
Contributor Author

CodeRabbit (@coderabbitai) review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/domains/runtimeEnv/managedEnvDir.test.ts`:
- Around line 42-46: The test assertion on the line with
expect(managedEnvDir()).toBe() is comparing against join("/custom/cache", hash)
but managedEnvDir() actually returns join(resolve("/custom/cache"), hash) as
shown in managedEnvDir.ts line 30. On Windows, resolve() adds a drive prefix
which causes the path mismatch. Update the expectation to use
resolve("/custom/cache") when calling join to match the actual behavior of
managedEnvDir() function and ensure the assertion correctly validates the
implementation across all platforms.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ee675fa0-4627-4bb1-896b-23a82ad0c3e8

📥 Commits

Reviewing files that changed from the base of the PR and between 445f49a and e9b905f.

⛔ Files ignored due to path filters (1)
  • src/commands/__snapshots__/help.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • src/commands/flows/hybridRunDefaults.ts
  • src/commands/flows/run.register.ts
  • src/commands/flows/runDefaults.handle.test.ts
  • src/commands/flows/runDefaults.ts
  • src/core/messages/runner.ts
  • src/domains/runtimeEnv/index.ts
  • src/domains/runtimeEnv/managedEnvDir.test.ts
  • src/domains/runtimeEnv/managedEnvDir.ts

Comment thread src/domains/runtimeEnv/managedEnvDir.test.ts
@michael-pr

Copy link
Copy Markdown
Contributor Author

CodeRabbit (@coderabbitai) review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

The managed runtime lives at <data-dir>/runtime/<hash> with no in-CLI way
to clear it, forcing a hand-typed `rm -rf` that risks deleting the wrong
path. Add `qawolf install clear`: a destructive, confirmed command that
removes the whole runtime base dir (honoring QAWOLF_RUNTIME_DIR), with a
`--yes` flag and structured json/agent output.
The destructive confirm used Clack's selectKey, which renders the message
inline on the prompt line. The embedded newline (path on its own line) broke
the framed timeline and garbled the y/n keystroke prompt so it could not be
answered. Move the path into a Clack note box and keep the confirm message to
a single line, matching the destructive-confirm pattern in init.
The destructive confirm used Clack's selectKey, a single-keystroke hotkey
prompt with no up/down navigation: arrows did nothing and Enter cancelled
instead of submitting, so the prompt felt frozen. Replace it with Clack's
standard arrow-navigable confirm, starting the cursor on No so a stray Enter
stays safe. Removes the selectKey path entirely; also fixes the same prompt
in init and flows pull.
Wrap the removal in withProgress so the user gets immediate feedback while
the directory is deleted, matching auth logout. Drop the path from the final
message — the confirm note already shows it (human) and the structured output
carries it in `dir` (json/agent).
… pre-bundle

Phases 1–3: Implement flow-resolution strategy for managed runtime.

- loadFlowDefault: Replace @qawolf/flows bare-import rewriting with
  Bun.build pre-bundling (compiled binary path) or direct import (Node
  path). Externalize native browser drivers so they resolve via the
  bundle root's node_modules symlink instead of being inlined.
  Remove depsRoot arg (no longer used); injection replaces env search.
  Delete rewriteFlowImports, findFlowsEnvDir, data: URI/sourceURL.

- runWebFlow, runAndroidFlow: Drop depsRoot arg to loadFlowDefault.

- New linkManagedDeps: Idempotent symlink <bundleRoot>/node_modules ->
  <depsRoot>/node_modules; exported from runtimeEnv/index.

- New stageFlows: Stage raw in-place projects into .qawolf/.local/<hash>
  (excluding node_modules/.git/.qawolf), remap flow paths. Symlink never
  pollutes user project; .qawolf bundles used in place.

- New copyDir: copyDirExcluding for entry-by-entry copy so destination
  may live inside source (avoids cp EINVAL).

- runDefaults, hybridRunDefaults: Wire stageFlows + linkManagedDeps
  (symlink only when runtimeEnv.source !== "project"). Pass staged
  files to flowsRun. hybridRun.test adds fs to ctx.
@michael-pr

Copy link
Copy Markdown
Contributor Author

CodeRabbit (@coderabbitai) review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/domains/flows/stageFlows.test.ts`:
- Around line 113-127: The test verifies that the staged file exists after
re-running stageFlows but does not verify that the updated content is actually
picked up. Add an assertion that reads the content of the staged file at
join(second.bundleRoot, "a.flow.ts") and verifies it equals "v2" (the updated
content), not "v1" (the original content), to ensure that edits to the original
flow file are properly reflected in the staged directory.

In `@src/domains/flows/stageFlows.ts`:
- Around line 42-45: The stagedDir variable is derived deterministically from
hashProjectDir, which causes concurrent runs with the same projectDir to collide
and delete each other's staging directories mid-execution. To fix this, modify
the stagedDir construction to include a unique identifier per run (such as a
timestamp or UUID) in addition to the hash, ensuring each concurrent execution
gets its own isolated staging directory that won't be removed by other
simultaneous runs. Alternatively, implement a file-based locking mechanism
around the fs.rm and fs.mkdir operations to serialize access to the same staging
directory across concurrent runs.

In `@src/domains/runtimeEnv/clearRuntimeEnv.ts`:
- Around line 12-14: The clearRuntimeEnv function performs a recursive deletion
on a directory path obtained from managedEnvBaseDir() without verifying it is
actually a managed runtime directory, which could lead to accidental deletion of
unrelated data if the environment override is misconfigured. Add validation
logic before the fs.rm call to verify the directory is a legitimate managed
runtime location (using a sentinel file or layout validation), and skip or fail
the deletion operation if validation is missing or fails. This ensures the
function fails closed and protects against dangerous deletions.

In `@src/domains/runtimeEnv/linkManagedDeps.ts`:
- Line 33: The symlink call in linkManagedDeps.ts is hardcoded to use "dir"
which requires administrator privileges on Windows. Replace the hardcoded "dir"
string with platform-aware logic that checks the current operating system: use
"junction" for Windows (which requires no elevated privileges) and "dir" for all
other platforms. The same conditional logic should also be applied to the
corresponding symlink call in the test file linkManagedDeps.test.ts at line 83
to keep the tests platform-aware and consistent with the production code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c65846db-936b-4cae-9b68-b799bae639b2

📥 Commits

Reviewing files that changed from the base of the PR and between 6a5ba87 and 9a37746.

⛔ Files ignored due to path filters (1)
  • src/commands/__snapshots__/help.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (26)
  • skills/qawolf-cli/SKILL.md
  • src/commands/flows/hybridRun.test.ts
  • src/commands/flows/hybridRunDefaults.ts
  • src/commands/flows/runDefaults.ts
  • src/commands/help.test.ts
  • src/commands/install/clear.ts
  • src/commands/install/index.ts
  • src/core/messages/install.ts
  • src/domains/flows/stageFlows.test.ts
  • src/domains/flows/stageFlows.ts
  • src/domains/runner/loadFlowDefault.test.ts
  • src/domains/runner/loadFlowDefault.ts
  • src/domains/runner/runAndroidFlow.ts
  • src/domains/runner/runWebFlow.ts
  • src/domains/runtimeEnv/clearRuntimeEnv.test.ts
  • src/domains/runtimeEnv/clearRuntimeEnv.ts
  • src/domains/runtimeEnv/index.ts
  • src/domains/runtimeEnv/linkManagedDeps.test.ts
  • src/domains/runtimeEnv/linkManagedDeps.ts
  • src/domains/runtimeEnv/managedEnvDir.ts
  • src/shell/copyDir.ts
  • src/shell/ui/clack/styledClack.mock.ts
  • src/shell/ui/clack/styledClack.ts
  • src/shell/ui/renderers/confirm.test.ts
  • src/shell/ui/renderers/confirm.ts
  • src/shell/ui/types.ts
💤 Files with no reviewable changes (2)
  • src/shell/ui/clack/styledClack.mock.ts
  • src/domains/runner/runAndroidFlow.ts

Comment thread src/domains/flows/stageFlows.test.ts
Comment thread src/domains/flows/stageFlows.ts Outdated
Comment thread src/domains/runtimeEnv/clearRuntimeEnv.ts Outdated
Comment thread src/domains/runtimeEnv/linkManagedDeps.ts Outdated
@michael-pr Michael Price (michael-pr) force-pushed the wiz-10907-potential-incompatibility-with-other-monorepo-and-single branch from 5dcd302 to 80fe8d8 Compare June 23, 2026 23:38
@michael-pr

Copy link
Copy Markdown
Contributor Author

CodeRabbit (@coderabbitai) review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/commands/flows/hybridRunDefaults.ts (1)

115-134: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Register staged cleanup before hybrid setup can throw.

After staging, linkManagedDeps, configureTestkit, or runWebFlowDeps can throw before cleanup is registered at line 134, leaking the staged .qawolf/.local tree. Move registration and the try/finally directly after stageFlows.

Proposed cleanup ordering
   const staged = await stageFlows({
     files,
     projectDir,
     cwd: process.cwd(),
     fs: ctx.fs,
   });
-  if (runtimeEnv.source !== "project" && staged.bundleRoot !== undefined) {
-    await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
-  }
-
-  const resolvedDir = runtimeEnv.depsRoot;
-  await resolvedDeps.configureTestkit(resolvedDir);
-  const android = createAndroidDeps(resolvedDir, ctx.signals);
-  const runWebFlowDeps = await resolvedDeps.runWebFlowDeps(
-    resolvedDir,
-    ctx.signals,
-  );
-
   const unregisterCleanup = staged.cleanup
     ? ctx.signals.register(staged.cleanup)
     : undefined;
   try {
+    if (runtimeEnv.source !== "project" && staged.bundleRoot !== undefined) {
+      await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
+    }
+
+    const resolvedDir = runtimeEnv.depsRoot;
+    await resolvedDeps.configureTestkit(resolvedDir);
+    const android = createAndroidDeps(resolvedDir, ctx.signals);
+    const runWebFlowDeps = await resolvedDeps.runWebFlowDeps(
+      resolvedDir,
+      ctx.signals,
+    );
+
     return await resolvedDeps.flowsRun(
       ctx,
       staged.files,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/flows/hybridRunDefaults.ts` around lines 115 - 134, The staged
cleanup is registered too late, after operations that can throw
(linkManagedDeps, configureTestkit, createAndroidDeps, runWebFlowDeps), which
can leak the staged .qawolf/.local tree if those operations fail. Move the
cleanup registration immediately after the stageFlows() call completes, then
wrap all subsequent code in a try/finally block that calls unregisterCleanup to
ensure cleanup runs regardless of whether later operations throw.
src/commands/flows/runDefaults.ts (1)

101-119: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Register staged cleanup before setup can throw.

After stageFlows, linkManagedDeps, configureTestkit, or runWebFlowDeps can throw before line 119 registers cleanup, leaving the staged .qawolf/.local tree behind. Register cleanup and enter the try/finally immediately after staging.

Proposed cleanup ordering
   const staged = await stageFlows({
     files: expandedFiles,
     projectDir,
     cwd,
     fs: ctx.fs,
   });
-  if (runtimeEnv.source !== "project" && staged.bundleRoot !== undefined) {
-    await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
-  }
-
-  const resolvedDir = runtimeEnv.depsRoot;
-
-  await resolvedDeps.configureTestkit(resolvedDir);
-  const android = createAndroidDeps(resolvedDir, ctx.signals);
-  const runWebFlowDeps = await resolvedDeps.runWebFlowDeps(
-    resolvedDir,
-    ctx.signals,
-  );
   const unregisterCleanup = staged.cleanup
     ? ctx.signals.register(staged.cleanup)
     : undefined;
   try {
+    if (runtimeEnv.source !== "project" && staged.bundleRoot !== undefined) {
+      await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
+    }
+
+    const resolvedDir = runtimeEnv.depsRoot;
+
+    await resolvedDeps.configureTestkit(resolvedDir);
+    const android = createAndroidDeps(resolvedDir, ctx.signals);
+    const runWebFlowDeps = await resolvedDeps.runWebFlowDeps(
+      resolvedDir,
+      ctx.signals,
+    );
+
     return await resolvedDeps.flowsRun(
       ctx,
       staged.files,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/flows/runDefaults.ts` around lines 101 - 119, The cleanup
registration using staged.cleanup happens too late in the function, after
stageFlows, linkManagedDeps, configureTestkit, and runWebFlowDeps have already
been called. If any of these operations throw an error before reaching the
cleanup registration at the end, the staged tree will be left behind. Move the
unregisterCleanup initialization to immediately after the stageFlows call
completes, then wrap all the remaining operations (linkManagedDeps,
configureTestkit, createAndroidDeps, runWebFlowDeps, and any subsequent code) in
a try/finally block to ensure cleanup is always registered and executed.
src/domains/runner/runWebFlow.ts (1)

44-47: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Include depsRoot in the runtime-init cache key.

This call now passes a per-run dependency root, but the supplied src/domains/runner/initFlowRuntime.ts context shows doInit imports _runner from options.depsRoot while initCache is keyed only by path.dirname(flowPath). A later run of the same flow directory with a different --deps/managed/project root can silently reuse the wrong runtime. Key the cache by both start dir and deps root.

Proposed fix in initFlowRuntime
 export function initFlowRuntime(
   flowPath: string,
   options: InitFlowRuntimeOptions,
   fs: Fs = makeDefaultFs(),
 ): Promise<void> {
   const startDir = path.dirname(flowPath);
+  const cacheKey = `${startDir}\0${options.depsRoot ?? ""}`;
   // Cache key is startDir, not fs — tests reusing the same startDir must call
   // _resetInitCache() between runs. Timeout is omitted deliberately: it is a
   // single run-global flag, so every flow in a process shares one value.
-  let p = initCache.get(startDir);
+  let p = initCache.get(cacheKey);
   if (!p) {
     p = doInit(flowPath, options.timeout, fs, options.depsRoot);
-    initCache.set(startDir, p);
+    initCache.set(cacheKey, p);
   }
   return p;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/domains/runner/runWebFlow.ts` around lines 44 - 47, The initFlowRuntime
function in src/domains/runner/initFlowRuntime.ts uses initCache keyed only by
path.dirname(flowPath), but the doInit function now uses options.depsRoot to
import the _runner dependency. This means different runs of the same flow
directory with different depsRoot values will incorrectly reuse the cached
runtime from a previous run with a different dependency root. Update the cache
key generation to include both path.dirname(flowPath) and options.depsRoot so
that each unique combination of flow directory and dependency root has its own
cached runtime instance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/flows/hybridRun.test.ts`:
- Line 55: The in-memory filesystem created by makeMemoryFs() is empty, causing
resolveProjectDirSafe() to return undefined and stageFlows() to skip the
staging/linking/cleanup path that needs to be tested. Seed the ctx.fs with the
necessary mocked project/environment files (such as configuration files or
manifest files that the project resolution logic expects) before calling the
functions that depend on resolveProjectDirSafe(), or alternatively create a
focused test case with a resolvable project directory structure.

In `@src/commands/flows/hybridRunDefaults.ts`:
- Around line 115-124: The current condition for linking managed dependencies at
the `linkManagedDeps` call is too restrictive - it checks both
`runtimeEnv.source !== "project"` and `staged.bundleRoot !== undefined`, which
means dependencies are skipped when bundleRoot is undefined or when source is
"project". This causes hybrid runs to load flows without the properly resolved
depsRoot. Remove the `runtimeEnv.source !== "project"` check so that
`linkManagedDeps` is invoked whenever `staged.bundleRoot` is defined,
guaranteeing that all hybrid runs have access to the managed dependencies
through the deps root selected by resolveDepsRoot.

In `@src/commands/flows/runDefaults.ts`:
- Around line 101-109: The condition for calling linkManagedDeps is too
restrictive and only executes when runtimeEnv.source is not "project". This
fails to ensure that staged flows always resolve imports from the correct deps
root. Modify the if condition to link whenever staged.bundleRoot exists,
regardless of the runtimeEnv.source value. Remove the source check so that
linkManagedDeps is called for all cases where a bundleRoot is available,
ensuring managed and override fallbacks are properly linked to
runtimeEnv.depsRoot.

In `@src/domains/runtimeEnv/clearRuntimeEnv.test.ts`:
- Around line 51-52: The test currently uses
expect(clearRuntimeEnv(fs)).rejects.toThrow() without properly settling the
promise before the dependent assertion on the next line. Replace the
expect().rejects pattern with an explicit try/catch block where you await the
clearRuntimeEnv(fs) call in the try block, then in the catch block verify the
error message contains "Refusing to delete" using an expect statement. Only
after the try/catch block completes should the filesystem assertion checking
fs.pathExists(override) execute, ensuring all dependent state checks run after
the promise has fully settled.

---

Outside diff comments:
In `@src/commands/flows/hybridRunDefaults.ts`:
- Around line 115-134: The staged cleanup is registered too late, after
operations that can throw (linkManagedDeps, configureTestkit, createAndroidDeps,
runWebFlowDeps), which can leak the staged .qawolf/.local tree if those
operations fail. Move the cleanup registration immediately after the
stageFlows() call completes, then wrap all subsequent code in a try/finally
block that calls unregisterCleanup to ensure cleanup runs regardless of whether
later operations throw.

In `@src/commands/flows/runDefaults.ts`:
- Around line 101-119: The cleanup registration using staged.cleanup happens too
late in the function, after stageFlows, linkManagedDeps, configureTestkit, and
runWebFlowDeps have already been called. If any of these operations throw an
error before reaching the cleanup registration at the end, the staged tree will
be left behind. Move the unregisterCleanup initialization to immediately after
the stageFlows call completes, then wrap all the remaining operations
(linkManagedDeps, configureTestkit, createAndroidDeps, runWebFlowDeps, and any
subsequent code) in a try/finally block to ensure cleanup is always registered
and executed.

In `@src/domains/runner/runWebFlow.ts`:
- Around line 44-47: The initFlowRuntime function in
src/domains/runner/initFlowRuntime.ts uses initCache keyed only by
path.dirname(flowPath), but the doInit function now uses options.depsRoot to
import the _runner dependency. This means different runs of the same flow
directory with different depsRoot values will incorrectly reuse the cached
runtime from a previous run with a different dependency root. Update the cache
key generation to include both path.dirname(flowPath) and options.depsRoot so
that each unique combination of flow directory and dependency root has its own
cached runtime instance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2133f2ee-17f7-4d5a-860a-6532bd983fe7

📥 Commits

Reviewing files that changed from the base of the PR and between 6a5ba87 and 80fe8d8.

⛔ Files ignored due to path filters (1)
  • src/commands/__snapshots__/help.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (27)
  • docs/plans/2026-06-23-runtime-deps-target-design.md
  • skills/qawolf-cli/SKILL.md
  • src/commands/flows/hybridRun.test.ts
  • src/commands/flows/hybridRunDefaults.ts
  • src/commands/flows/runDefaults.ts
  • src/commands/help.test.ts
  • src/commands/install/clear.ts
  • src/commands/install/index.ts
  • src/core/messages/install.ts
  • src/domains/flows/stageFlows.test.ts
  • src/domains/flows/stageFlows.ts
  • src/domains/runner/loadFlowDefault.test.ts
  • src/domains/runner/loadFlowDefault.ts
  • src/domains/runner/runAndroidFlow.ts
  • src/domains/runner/runWebFlow.ts
  • src/domains/runtimeEnv/clearRuntimeEnv.test.ts
  • src/domains/runtimeEnv/clearRuntimeEnv.ts
  • src/domains/runtimeEnv/index.ts
  • src/domains/runtimeEnv/linkManagedDeps.test.ts
  • src/domains/runtimeEnv/linkManagedDeps.ts
  • src/domains/runtimeEnv/managedEnvDir.ts
  • src/shell/copyDir.ts
  • src/shell/ui/clack/styledClack.mock.ts
  • src/shell/ui/clack/styledClack.ts
  • src/shell/ui/renderers/confirm.test.ts
  • src/shell/ui/renderers/confirm.ts
  • src/shell/ui/types.ts
💤 Files with no reviewable changes (2)
  • src/domains/runner/runAndroidFlow.ts
  • src/shell/ui/clack/styledClack.mock.ts

Comment thread src/commands/flows/hybridRun.test.ts
Comment on lines +115 to +124
const projectDir = resolveProjectDirSafe(files, ctx.fs);
const staged = await stageFlows({
files,
projectDir,
cwd: process.cwd(),
fs: ctx.fs,
});
if (runtimeEnv.source !== "project" && staged.bundleRoot !== undefined) {
await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
}

@coderabbitai coderabbitai Bot Jun 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Guarantee hybrid staged flows resolve through runtimeEnv.depsRoot.

stageFlows excludes node_modules, but line 122 links only non-project sources and does nothing when projectDir is unresolved and bundleRoot is undefined. Hybrid runs can then load staged/original flows without the deps root selected by resolveDepsRoot. Ensure every managed/override fallback has a bundle root, then link whenever one exists.

Minimum condition fix once a bundle root exists
-  if (runtimeEnv.source !== "project" && staged.bundleRoot !== undefined) {
+  if (staged.bundleRoot !== undefined) {
     await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const projectDir = resolveProjectDirSafe(files, ctx.fs);
const staged = await stageFlows({
files,
projectDir,
cwd: process.cwd(),
fs: ctx.fs,
});
if (runtimeEnv.source !== "project" && staged.bundleRoot !== undefined) {
await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
}
const projectDir = resolveProjectDirSafe(files, ctx.fs);
const staged = await stageFlows({
files,
projectDir,
cwd: process.cwd(),
fs: ctx.fs,
});
if (staged.bundleRoot !== undefined) {
await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/flows/hybridRunDefaults.ts` around lines 115 - 124, The current
condition for linking managed dependencies at the `linkManagedDeps` call is too
restrictive - it checks both `runtimeEnv.source !== "project"` and
`staged.bundleRoot !== undefined`, which means dependencies are skipped when
bundleRoot is undefined or when source is "project". This causes hybrid runs to
load flows without the properly resolved depsRoot. Remove the `runtimeEnv.source
!== "project"` check so that `linkManagedDeps` is invoked whenever
`staged.bundleRoot` is defined, guaranteeing that all hybrid runs have access to
the managed dependencies through the deps root selected by resolveDepsRoot.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — this is the staged-flow resolution flaw (Blocker 1) called out in the approved target design (docs/plans/2026-06-23-runtime-deps-target-design.md). Slice 3 replaces the stageFlows + linkManagedDeps model with a layered node_modules (executor in a shared cached parent, the flow's own deps in a per-run child resolved via walk-up), which fixes this wholesale across both channels. Leaving open to be addressed by that redesign rather than band-aiding code that is about to be replaced.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michael-pr — understood. The layered node_modules approach in Slice 3 addresses this more cleanly than a conditional patch here.

Would you like me to open a GitHub tracking issue for this so it doesn't get lost between slices? I can link it back to this comment and the design document.

Comment on lines +101 to 109
const staged = await stageFlows({
files: expandedFiles,
projectDir,
cwd,
fs: ctx.fs,
});
if (runtimeEnv.source !== "project" && staged.bundleRoot !== undefined) {
await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
}

@coderabbitai coderabbitai Bot Jun 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Guarantee staged flows resolve through runtimeEnv.depsRoot.

stageFlows copies the project under .qawolf/.local without node_modules; line 107 links only non-project sources, and when resolveProjectDirSafe returns undefined, stageFlows returns no bundleRoot to link at all. Both cases can leave the loaded flow resolving imports from the wrong tree instead of the selected deps root. Make staging produce a bundle root for managed/override fallbacks, and link whenever a bundle root exists.

Minimum condition fix once a bundle root exists
-  if (runtimeEnv.source !== "project" && staged.bundleRoot !== undefined) {
+  if (staged.bundleRoot !== undefined) {
     await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const staged = await stageFlows({
files: expandedFiles,
projectDir,
cwd,
fs: ctx.fs,
});
if (runtimeEnv.source !== "project" && staged.bundleRoot !== undefined) {
await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
}
const staged = await stageFlows({
files: expandedFiles,
projectDir,
cwd,
fs: ctx.fs,
});
if (staged.bundleRoot !== undefined) {
await linkManagedDeps(staged.bundleRoot, runtimeEnv.depsRoot, ctx.fs);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/flows/runDefaults.ts` around lines 101 - 109, The condition for
calling linkManagedDeps is too restrictive and only executes when
runtimeEnv.source is not "project". This fails to ensure that staged flows
always resolve imports from the correct deps root. Modify the if condition to
link whenever staged.bundleRoot exists, regardless of the runtimeEnv.source
value. Remove the source check so that linkManagedDeps is called for all cases
where a bundleRoot is available, ensuring managed and override fallbacks are
properly linked to runtimeEnv.depsRoot.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — this is the staged-flow resolution flaw (Blocker 1) called out in the approved target design (docs/plans/2026-06-23-runtime-deps-target-design.md). Slice 3 replaces the stageFlows + linkManagedDeps model with a layered node_modules (executor in a shared cached parent, the flow's own deps in a per-run child resolved via walk-up), which fixes this wholesale across both channels. Leaving open to be addressed by that redesign rather than band-aiding code that is about to be replaced.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michael-pr, understood — deferring to Slice 3's layered node_modules redesign is the right call given the code is being replaced. Would you like me to open a GitHub issue to track this as a known limitation until Slice 3 lands?

Comment on lines +51 to +52
expect(clearRuntimeEnv(fs)).rejects.toThrow("Refusing to delete");
expect(await fs.pathExists(override)).toBe(true);

@coderabbitai coderabbitai Bot Jun 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use explicit await flow for dependent rejection assertions.

Line 51 uses expect(promise).rejects... and Line 52 immediately checks dependent state. Convert this to try/catch with await clearRuntimeEnv(fs) so the failure path is settled before the filesystem assertion.

Suggested fix
-    expect(clearRuntimeEnv(fs)).rejects.toThrow("Refusing to delete");
+    try {
+      await clearRuntimeEnv(fs);
+      throw new Error("Expected clearRuntimeEnv to throw");
+    } catch (error) {
+      expect((error as Error).message).toContain("Refusing to delete");
+    }
     expect(await fs.pathExists(override)).toBe(true);
As per coding guidelines, "In bun:test, `expect(promise).rejects.toThrow()` does not require `await` unless you have dependent assertions that need the promise to settle first. If dependent assertions exist, use try-catch to explicitly await the promise instead."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(clearRuntimeEnv(fs)).rejects.toThrow("Refusing to delete");
expect(await fs.pathExists(override)).toBe(true);
try {
await clearRuntimeEnv(fs);
throw new Error("Expected clearRuntimeEnv to throw");
} catch (error) {
expect((error as Error).message).toContain("Refusing to delete");
}
expect(await fs.pathExists(override)).toBe(true);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/domains/runtimeEnv/clearRuntimeEnv.test.ts` around lines 51 - 52, The
test currently uses expect(clearRuntimeEnv(fs)).rejects.toThrow() without
properly settling the promise before the dependent assertion on the next line.
Replace the expect().rejects pattern with an explicit try/catch block where you
await the clearRuntimeEnv(fs) call in the try block, then in the catch block
verify the error message contains "Refusing to delete" using an expect
statement. Only after the try/catch block completes should the filesystem
assertion checking fs.pathExists(override) execute, ensuring all dependent state
checks run after the promise has fully settled.

Source: Coding guidelines

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted — this sits in the stageFlows/runtime-deps code that the approved redesign (docs/plans/2026-06-23-runtime-deps-target-design.md, Slice 3) rewrites. Folding it into that work rather than patching code about to be replaced.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michael-pr, understood — no point patching code that Slice 3 rewrites. Would you like me to open a GitHub issue to track the try/catch fix for the dependent rejection assertions in clearRuntimeEnv.test.ts so it isn't lost when that slice lands?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant